#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h> 

#define TRUE 1
#define FALSE 0

const char PATTERN = 0x5A; //01011010 = Z

char Usage[] = "\
Usage: traffic_sink [-options] listen_port\n\
Options:\n\
   -n   Negative Acknowledgement - Check the content of the messages to fit\n\
                                   a certain predetermined message to see\n\
                                   contents weren't scrambled in flight\n";
   
//used to leave the program with a message
void leave(const char *msg)
{
    puts(msg);
    exit(1);
}

//used to leave the program in an error case
void error(const char *msg)
{
    perror(msg);
    exit(2);
}

void usage(const char *msg)
{
   puts(msg);
   puts(Usage);
   exit(3);
}

//grabs the string form of an ip address from a IPv4 or IPv6 sockaddr
char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen)
{
   switch(sa->sa_family) 
   {
     case AF_INET:
         inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
                 s, maxlen);
         break;

     case AF_INET6:
         inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
                 s, maxlen);
         break;

     default:
         strncpy(s, "Unknown AF", maxlen);
         return NULL;
   }

   return s;
}

int main(int argc, char* argv[])
{
   const int BUFFER_LENGTH = 50000;
   const int OUTGOING_MESSAGE_LENGTH = 7;
   
   int neg_ack = FALSE;
   
   int sockfd;
   long listen_port;
   int i,itemp,nbytes,sockaddr_in_size;
   int a,b,d,e;
   int nakd;
   long ltemp;
   struct sockaddr_in this_addr,from_addr;
   char addr[INET6_ADDRSTRLEN];
   char buffer[BUFFER_LENGTH];
   unsigned int *id_pt,id;
   char *ctemp,c;
   
   id_pt = (int*)buffer;
   
   while ((c = getopt(argc,argv,"n")) != -1)
   {
      switch (c)
      {
         case 'n':
            neg_ack = TRUE;
            break;
         default:
            usage("Invalid options chosen");
      }
   }   
   
   if (argc - optind < 1)
   {
      usage("Invalid amount of arguments");
   }
   else
   {
      listen_port = strtol(argv[optind],&ctemp,0);
      if (*ctemp != NULL || listen_port > 65535 || listen_port < 1024)
         leave("Invalid listening port number use 1024-65535");
   }
   //set up socket
   if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
      error("ERROR opening socket");
   
   //set up server listening addr
   bzero((char *) &this_addr, sizeof(this_addr));
   this_addr.sin_family = AF_INET;
   this_addr.sin_addr.s_addr = htonl(INADDR_ANY);
   this_addr.sin_port = htons(listen_port);
   
   //bind to the socket
   if (bind(sockfd, (struct sockaddr *) &this_addr, sizeof(this_addr)) < 0)
      error("ERROR on binding");
   
   sockaddr_in_size = sizeof(from_addr);
   
   //main loop that receives messages and then returns an acknowledgement
   while (1)
   {
      bzero(buffer,BUFFER_LENGTH);
      
      //wait for information on the socket and then retrieve it.
      if ((nbytes = recvfrom(sockfd,buffer,BUFFER_LENGTH-1,0,(struct sockaddr *) &from_addr, &sockaddr_in_size)) < 0)
         error("ERROR reading from socket");

      //if its a summary - print it out
      if (strncmp(buffer,"Summary:",8) == 0)
      {
         printf("Received Summary from %s port %d - \n",get_ip_str((struct sockaddr *)&from_addr,addr,sizeof(from_addr)),ntohs(from_addr.sin_port));
         
         printf(buffer);
         continue;
      }

      //copy message id   
      id = ntohl(id_pt[0]);
      
      //visual identifier for when we receive a message.
      //printf("Received message %u of %d bytes\n", id, nbytes);
      // buffer[nbytes-1] = '\0';
      // ctemp = buffer + 4;
      // puts(ctemp);
      
      //if we are checking the messages contents and sending negative acknowledgements, 
      //it should go through and check each bit in the buffer (other than the message id)
      //to see if it fits the correct pattern.  
      if (neg_ack)
      {
         nakd = FALSE;
         for (i = 4; i < nbytes; i++)
         {
            if (buffer[i] != PATTERN)
            {
               memcpy(buffer+4,"NAK",3);
               if ((nbytes = sendto(sockfd,buffer,OUTGOING_MESSAGE_LENGTH,0,(struct sockaddr *) &from_addr, sizeof(from_addr))) < 0)
                  error("ERROR writing to socket");   
               nakd = TRUE;
               break;
            }
         }
         if (nakd)
            continue;
      }
      
      //send an acknowledgement back
      memcpy(buffer+4,"ACK",3);
      if ((nbytes = sendto(sockfd,buffer,OUTGOING_MESSAGE_LENGTH,0,(struct sockaddr *) &from_addr, sizeof(from_addr))) < 0)
         error("ERROR writing to socket");
   }
   
   free(buffer);
   
   return 0;
}
